package com.beeasy.hzbpm.ctrl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.beeasy.hzbpm.bean.Doc;
import com.beeasy.hzbpm.bean.JsEngine;
import com.beeasy.hzbpm.bean.UnDoc;
import com.beeasy.hzbpm.util.Result;
import com.github.llyb120.json.Arr;
import com.github.llyb120.json.Json;
import com.github.llyb120.json.Obj;
import com.github.llyb120.json.Validate;
import com.mongodb.client.MongoCollection;
import org.bson.Document;
import org.bson.types.ObjectId;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.beeasy.hzbpm.bean.MongoService.db;
import static com.github.llyb120.json.Json.*;

public class report {

    private MongoCollection<Document> catdb = db.getCollection("reportCat");
    private MongoCollection<Document> reportdb = db.getCollection("reportModel");

    @Doc
    public Object list() {
        Arr arr = catdb.aggregate(a(
            o("$lookup", o(
                "from", "reportModel",
                "let", o(
                    "pid", "$_id"
                ),
                "as", "children",
                "pipeline", a(
                    o(
                        "$match", o(
                            "$expr", o(
                                "$eq", a("$pid", "$$pid")
                            )
                        )
                    ),
                    o(
                        "$project", o(
                            "_id", 0,
                            "id", o(
                                "$toString", "$_id"
                            ),
                            "pid", o(
                                "$toString", "$pid"
                            ),
                            "type", "report",
                            "text", "$name"
                        )
                    )
                )
            )),
            o("$project", o(
                "_id", 0,
                "id", o("$toString", "$_id"),
                "text", "$name",
                "type", "cat",
                "children", 1
            ))
        ).toBson()).into(a());
        return Result.ok(arr);
    }

    @Doc
    public Object renameCat(Obj params){
        params.put("type", "cat");
        return rename(params);
    }

    public Object addCat() {
        Obj obj = o(
            "pid", null,
            "name", "新分类"
        );
        Document doc = obj.toBson();
        catdb.insertOne(doc);
        doc.put("text", doc.getString("name"));
        doc.remove("name");
        doc.put("type", "cat");
        doc.put("id", doc.getObjectId("_id").toString());
        doc.remove("_id");
        return Result.ok(doc);
    }

    @Doc
    public Object delCat(String id){
        return del("cat", id);
    }

    @Doc
    public Object delModel(String id){
        return del("report", id);
    }

    @UnDoc
    public Object del(String type, String id) {
//        String type = $request.s("type", "");
        if ("cat".equals(type)) {
            catdb.deleteOne(o("_id", new ObjectId(id)).toBson());
        } else if ("report".equals(type)) {
            reportdb.deleteOne(o("_id", new ObjectId(id)).toBson());
        }
        return Result.ok();
    }

    @UnDoc
    public Object rename(Obj params) {
        params.v("name", Validate.NotBlank, "不能为空");
        if (params.s("type", "").equals("cat")) {
            catdb.updateOne(o(
                "_id", new ObjectId(params.s("id"))
            ).toBson(), o(
                "$set", o(
                    "name", params.s("name")
                )
            ).toBson());
        }
        return Result.ok();
    }

    @Doc
    public Object addModel(Obj body){
        return saveModel(body);
    }

    @Doc
    public Object editModel(Obj body){
        return saveModel(body);
    }

    @UnDoc
    public Object saveModel(Obj body) {
        Document doc = null;
        String pid = body.s("pid", "");
        String id = body.s("_id", "");
        if (pid.isEmpty() && id.isEmpty()) {
            return Result.error("ffff");
        }
        body.put("pid", new ObjectId(pid));
        if (!id.isEmpty()) {
            body.put("_id", new ObjectId(body.s("_id")));
            reportdb.replaceOne(o(
                "_id", body.get("_id")
            ).toBson(), doc = body.toBson());
        } else {
            body.remove("_id");
            doc = body.toBson();
            reportdb.insertOne(doc);
        }
        doc.put("_id", doc.getObjectId("_id").toString());
        doc.put("pid", doc.getObjectId("pid").toString());
        return Result.ok(doc);
    }

    @Doc
    public Object oneModel(String id) {
        Document doc = reportdb.find(o("_id", new ObjectId(id)).toBson()).first();
        doc.put("_id", doc.getObjectId("_id").toString());
        doc.put("pid", doc.getObjectId("pid").toString());
        return Result.ok(doc);
    }


    private String getGroupFieldName(String str) {
        if (str.equals("流程创建时间")) {
            return "$createTime";
        } else {
            return "$attrs." + str;
        }
    }

    private Object getGroupField(String type, String target, Obj args) {
        if (type.equals("时间分组")) {
            Integer loop = null;
            try{
                loop = args.i("dateLoop");
            } catch (Exception e){}
            if (loop == null) {
                loop = 0;
            }
            if (loop > 0) {
                return o(
                    "$subtract", a(
                        o("$subtract", a(getGroupFieldName(target), new Date(0))),
                        o("$mod", a(
                            o("$subtract", a(getGroupFieldName(target), new Date(0))),
                            loop * 1000
//                            1000 * 60 * 30 /*聚合时间段，30分钟*/
                        ))
                    )
                );
            }

            return o(
                "$dateToString", o(
                    "format", args.get("dateFormat"),
                    "date", getGroupFieldName(target)
                )
            );
        } else if (type.equals("字段分组")) {
            return getGroupFieldName(target);
        } else if (type.equals("分组计数")) {
            return o("$sum", 1);
        } else {
            return null;
        }
    }

    public Object oneReportCondition(String id) {
        Document doc = reportdb.aggregate(
            a(
                o("$match", o("_id", new ObjectId(id))),
                o("$project", o("rules", 1))
            ).toBson()
        ).first();
        if (doc == null) {
            return Result.error("查询不到报表模型");
        }
        List<Document> rules = (List<Document>) doc.get("rules");
        return Result.ok(rules);
    }

    public Object oneReport(String id, Obj args) {
        if (args == null) {
            args = o();
        }
        Document doc = reportdb.find(o("_id", new ObjectId(id)).toBson()).first();
        if (doc == null) {
            return Result.error("查询不到报表模型");
        }
        args.remove("id");
        //查询所有符合条件的数据
        String bpmModelId = (String) doc.get("dataSource");
        MongoCollection<Document> col = db.getCollection("bpmInstance");
        List<Document> reports = (List<Document>) doc.get("reports");
        List<List<Document>> groups = (List<List<Document>>) doc.get("groups");
        List<Document> rules = (List<Document>) doc.get("rules");
        List<Document> collect = (List<Document>) doc.get("collect");
        //汇总字段求值
        if (collect == null) {
            collect = a();
        }
        Obj $match = o(
            "bpmId", new ObjectId(bpmModelId),
            "state", args.s("state", "").isEmpty() ? undefined : args.s("state")
        );
        Arr $and = a();
        for (Document rule : rules) {
            Object value = rule.getString("value");
            //如果设定了默认值，则只使用默认的
            if (StrUtil.isBlankIfStr(value)) {
                value = args.s(rule.getString("name"));
            }
            //忽略掉空参数
            if (value == null || StrUtil.isBlankIfStr(value)) {
                continue;
            }
            String type = rule.getString("type");
            if ("date".equals(type)) {
                value = DateUtil.parse((String) value, "yyyy-MM-dd").toJdkDate();
            }
            String op = rule.getString("op");
            String field = rule.getString("field");
            Obj ops = o(
                "等于", "$eq",
                "大于", "$gt",
                "小于", "$lt",
                "大于等于", "$gte",
                "小于等于", "$lte"
            );
            $and.add(o(getGroupFieldName(field).substring(1), o(ops.s(op), value)));
        }
        if (!$and.isEmpty()) {
            $match.put("$and", $and);
        }

        Obj groupIndexCache = o();
        List<List<Document>> datas = a();
        //自定义字段拉取
        for (int i = 0; i < groups.size(); i++) {
            Obj $group = o();
            Obj $project = o();
            Obj _id = o();
            Obj $sort = o();
            for (Document g : groups.get(i)) {
                String type = g.getString("type");
                String name = g.getString("name");
                String target = g.getString("target");
                String sort = g.getString("sort");
                groupIndexCache.put(name, i);
//                if ("asc".equals(sort)) {
//                    $sort.put(getGroupFieldName(target).substring(1), -1);
//                } else if ("desc".equals(sort)) {
//                    $sort.put(getGroupFieldName(target).substring(1), 1);
//                }
//                String isGroupBy = g.getString("isGroupBy");
                Obj _g = ooo(g);
                if (type.equals("时间分组") || type.equals("字段分组")) {
                    Integer dateLoop = null;
                    try{
                        dateLoop = _g.i("dateLoop");
                    } catch (Exception e){}
                    _id.put(name, getGroupField(type, target, _g));
                    if (dateLoop != null) {
                        //在最终格式化
                        $project.put(name, o(
                                "$dateToString", o(
                                    "format", _g.s("dateFormat"),
                                    "date", o(
                                        "$toDate", "$_id." + name
                                    )
                                )
                            )
                        );
                    } else {
                        $project.put(name, "$_id." + name);
                    }
                } else if (!type.contains("函数-")) {
                    $group.put(name, getGroupField(type, target, _g));
                    $project.put(name, "$" + name);
                }

                if ("asc".equals(sort)) {
                    $sort.put(name, 1);
                } else if ("desc".equals(sort)) {
                    $sort.put(name, -1);
                }
            }
            if ($sort.isEmpty()) {
                $sort.put("_id", -1);
            }
            $group.put("_id", _id);
            Arr arr = col.aggregate(a(
                o("$match", $match),
                o("$group", $group),
                o("$project", $project),
                o("$sort", $sort)
            ).toBson()).into(a());
            datas.add((List)arr);
        }

        //预解析汇总字段


        Arr rets = a();
        //检查是否需要拉取原有数据
        for (Document report : reports) {
            Obj _report = ooo(report);
            String type = report.getString("type");
            if (!type.equals("excel")) {
                rets.add(getPieData(_report, datas));
                continue;
            }
            List<List<String>> data = (List<List<String>>) report.get("data");
            Arr result = a();
            for (List<String> datum : data) {
                List used = null;
                String usedType = "";
                check:
                for (String s : datum) {
                    if (s != null && s.startsWith("[") && s.endsWith("]")) {
                        s = s.substring(1, s.length() - 1).trim();
                        //检查是否属于分组字段
                        for (int i = 0; i < groups.size(); i++) {
                            for (Document document : groups.get(i)) {
                                if (document.getString("name").equals(s)) {
                                    used = datas.get(i);
                                    break check;
                                }
                            }
                        }
//                        for (List list : datas) {
//                            if (!list.isEmpty()) {
//                                Document first = (Document) list.get(0);
//                                if (first.containsKey(s)) {
//                                    used = list;
//                                    usedType = "group";
//                                    break check;
//                                }
//                            }
//                        }
                        used = col.aggregate(
                            a(
                                o("$match", $match),
                                o("$project", o(
                                    "attrs", 1,
                                    "createTime", 1
                                ))
                            ).toBson()
                        ).into(a())
                            .stream()
                            .filter(e -> e != null)
                            .map(e -> {
                                Map map = (Map) e.get("attrs");
                                map.put("流程创建时间", e.get("createTime"));
                                return map;
                            })
                            .collect(Collectors.toList());
                        usedType = "data";
                        break check;
                    }
                }
                if (used != null) {
                    //第一排放入标题
                    Arr titles = a();
                    for (String s : datum) {
                        if (s != null && s.startsWith("[") && s.endsWith("]")) {
                            s = s.substring(1, s.length() - 1).trim();
                        }
                        titles.add(s);
                    }
                    result.add(titles);
                    for (Object o : used) {
                        Document document = (Document) o;
                        String single = null;
                        ;
                        String datasStr = null;
                        ;
                        Arr arr = a();
                        for (String s : datum) {
                            if (s != null && s.startsWith("[") && s.endsWith("]")) {
                                s = s.substring(1, s.length() - 1).trim();
                            }
                            //查找该字段来源
                            boolean flag = false;
                            for (Document document1 : collect) {
                                if (document1.getString("name").equals(s)) {
                                    if (single == null) {
                                        single = Json.stringify(document);
                                    }
                                    if (datasStr == null) {
                                        datasStr = Json.stringify(datas);
                                    }
                                    arr.add(getCollectValue(document1, datas.indexOf(used), datasStr, single, groups, groupIndexCache));
                                    flag = true;
                                    break;
                                }
                            }
                            if (!flag) {
                                Object value = document.get(s);
                                if (value != null) {
                                    if (value instanceof String && ((String) value).startsWith("[json]")) {
                                        Obj obj = Json.parse(((String) value).substring("[json]".length()));
                                        value = (String) obj.values()
                                            .stream()
                                            .map(e -> (String) e)
                                            .collect(Collectors.joining(","));
                                    }
                                }
                                arr.add(value);
                            }
                        }
                        result.add(arr);
                    }

                } else {
                    result.add(datum);
                }

            }

            rets.add(o(
                "type", report.get("type"),
                "data", result,
                "meta", report.get("meta"),
                "merge", report.get("merge"),
                "name", report.get("name")
            ));
        }

        return Result.ok(rets);
    }

    private Object getCollectValue(Document document, int keyIndex, String datas, String single, List groups, Obj groupIndexCache) {
        if (keyIndex == -1) {
            return "";
        }
        Pattern pattern = Pattern.compile("\\[(.+?)\\]");
        StringBuilder sb = new StringBuilder();
        String collectName = document.getString("name");
        String condition = document.getString("condition");
        String expression = document.getString("expression");
        if (StrUtil.isBlank(collectName) || StrUtil.isBlank(condition) || StrUtil.isBlank(expression)) {
            return "";
        }
        //加入循环构造方法体
        String varName = "_groupDatas";
        String singleVarName = "_singleData";
        sb.append(String.format("(function(){ var %s = %s; var %s = %s;", varName, (datas), singleVarName, single));
        for (int i = 0; i < groups.size(); i++) {
            if (i == keyIndex) {
                continue;
            }
            sb.append(String.format("for(var i%d = 0; i%d < %s[%d].length; i%d++){", i, i, varName, i, i));
        }
        StringBuffer ssb = new StringBuffer();
        ssb.append("if(");
        Matcher matcher = pattern.matcher(condition);
        while (matcher.find()) {
            String key = matcher.group(1).trim();
            //检查在哪个group中
            int idex = groupIndexCache.i(key);
            if (idex == keyIndex) {
                matcher.appendReplacement(ssb, String.format("%s['%s']", singleVarName, key));
            } else {
                matcher.appendReplacement(ssb, String.format("%s[%d][i%d]['%s']", varName, idex, idex, key));
            }
        }
        matcher.appendTail(ssb);
        ssb.append("){ return ");
        matcher = pattern.matcher(expression);
        while (matcher.find()) {
            String key = matcher.group(1).trim();
            //检查在哪个group中
            int idex = groupIndexCache.i(key);
            if (idex == keyIndex) {
                matcher.appendReplacement(ssb, String.format("%s['%s']", singleVarName, key));
            } else {
                matcher.appendReplacement(ssb, String.format("%s[%d][i%d]['%s']", varName, idex, idex, key));
            }
        }
        matcher.appendTail(ssb);
        ssb.append("; }");
        sb.append(ssb);
        for (int i = 0; i < groups.size(); i++) {
            if (i == keyIndex) {
                continue;
            }
            sb.append("}");
        }
        sb.append("})()");

        return JsEngine.calExpression(o(), sb.toString());
    }



    private Object getPieData(Obj report, List<List<Document>> groups){
        String x = report.s("x", "");
        String y = report.s("y", "");
        if(x.isEmpty() || y.isEmpty()){
            return null;
        }
        Obj ret = o(
            "legendData", a(),
            "seriesData", a(),
            "selected", o(),
            "type", report.s("type"),
            "name", report.s("name"),
            "x", report.s("x")
        );
        for (List<Document> group : groups) {
            if(group.size() > 0 && group.get(0).containsKey(x)) {
                ret.a("seriesData").addAll(
                    group.stream()
                        .peek(e -> ret.a("legendData").add(e.get(x)))
                        .peek(e -> ret.o("selected").put(e.getString(x), true))
                        .map(e -> o("name", e.get(x), "value", e.get(y)))
                        .collect(Collectors.toList())
                );
            }
        }
        return ret;
    }
}
